/**
 * Worker Pool
 * 
 * Manages a pool of Bun Workers for parallel execution
 */

import { Worker } from 'worker_threads';
import { logger } from '@/utils/logger';

/**
 * Worker task
 */
export interface WorkerTask<T = any, R = any> {
  id: string;
  type: string;
  data: T;
  resolve: (value: R) => void;
  reject: (error: Error) => void;
  timeout?: number;
}

/**
 * Worker pool options
 */
export interface WorkerPoolOptions {
  /**
   * Minimum number of workers to keep alive
   */
  minWorkers?: number;

  /**
   * Maximum number of workers
   */
  maxWorkers?: number;

  /**
   * Worker script path
   */
  workerScript: string;

  /**
   * Worker options
   */
  workerOptions?: {
    smol?: boolean;
    ref?: boolean;
  };

  /**
   * Task timeout in milliseconds
   */
  taskTimeout?: number;
}

/**
 * Worker info
 */
interface WorkerInfo {
  worker: Worker;
  busy: boolean;
  taskCount: number;
  createdAt: number;
}

/**
 * Worker Pool
 * 
 * Manages a pool of workers for parallel task execution
 */
export class WorkerPool {
  private workers: Map<number, WorkerInfo> = new Map();
  private taskQueue: WorkerTask[] = [];
  private nextWorkerId = 0;
  private options: Required<WorkerPoolOptions>;

  constructor(options: WorkerPoolOptions) {
    this.options = {
      minWorkers: options.minWorkers ?? 2,
      maxWorkers: options.maxWorkers ?? 4,
      workerScript: options.workerScript,
      workerOptions: options.workerOptions ?? {},
      taskTimeout: options.taskTimeout ?? 30000,
    };

    // Initialize minimum workers
    this.initializeWorkers();
  }

  /**
   * Initialize minimum workers
   */
  private initializeWorkers(): void {
    for (let i = 0; i < this.options.minWorkers; i++) {
      this.createWorker();
    }

    logger.info('Worker pool initialized', {
      minWorkers: this.options.minWorkers,
      maxWorkers: this.options.maxWorkers,
    });
  }

  /**
   * Create a new worker
   */
  private createWorker(): number {
    const workerId = this.nextWorkerId++;

    const worker = new Worker(this.options.workerScript, {
      ...this.options.workerOptions,
    });

    const workerInfo: WorkerInfo = {
      worker,
      busy: false,
      taskCount: 0,
      createdAt: Date.now(),
    };

    // Handle worker messages
    worker.on('message', (message) => {
      this.handleWorkerMessage(workerId, message);
    });

    // Handle worker errors
    worker.on('error', (error) => {
      logger.error('Worker error', { workerId, error });
      this.removeWorker(workerId);
    });

    // Handle worker exit
    worker.on('exit', (code) => {
      logger.info('Worker exited', { workerId, code });
      this.removeWorker(workerId);
    });

    this.workers.set(workerId, workerInfo);

    logger.info('Worker created', { workerId, totalWorkers: this.workers.size });

    return workerId;
  }

  /**
   * Handle worker message
   */
  private handleWorkerMessage(workerId: number, message: any): void {
    const workerInfo = this.workers.get(workerId);
    if (!workerInfo) {
      return;
    }

    // Mark worker as available
    workerInfo.busy = false;
    workerInfo.taskCount++;

    // Process next task in queue
    this.processNextTask();
  }

  /**
   * Remove a worker
   */
  private removeWorker(workerId: number): void {
    const workerInfo = this.workers.get(workerId);
    if (!workerInfo) {
      return;
    }

    try {
      workerInfo.worker.terminate();
    } catch (error) {
      logger.error('Failed to terminate worker', { workerId, error });
    }

    this.workers.delete(workerId);

    logger.info('Worker removed', { workerId, remainingWorkers: this.workers.size });

    // Ensure minimum workers
    if (this.workers.size < this.options.minWorkers) {
      this.createWorker();
    }
  }

  /**
   * Get an available worker
   */
  private getAvailableWorker(): number | null {
    // Find an idle worker
    for (const [workerId, workerInfo] of this.workers.entries()) {
      if (!workerInfo.busy) {
        return workerId;
      }
    }

    // Create a new worker if under max limit
    if (this.workers.size < this.options.maxWorkers) {
      return this.createWorker();
    }

    return null;
  }

  /**
   * Process next task in queue
   */
  private processNextTask(): void {
    if (this.taskQueue.length === 0) {
      return;
    }

    const workerId = this.getAvailableWorker();
    if (workerId === null) {
      return;
    }

    const task = this.taskQueue.shift();
    if (!task) {
      return;
    }

    this.executeTask(workerId, task);
  }

  /**
   * Execute a task on a worker
   */
  private executeTask<T, R>(workerId: number, task: WorkerTask<T, R>): void {
    const workerInfo = this.workers.get(workerId);
    if (!workerInfo) {
      task.reject(new Error(`Worker ${workerId} not found`));
      return;
    }

    workerInfo.busy = true;

    // Set timeout
    const timeoutId = setTimeout(() => {
      task.reject(new Error(`Task ${task.id} timed out`));
      workerInfo.busy = false;
      this.processNextTask();
    }, task.timeout ?? this.options.taskTimeout);

    // Send task to worker
    try {
      workerInfo.worker.postMessage({
        id: task.id,
        type: task.type,
        data: task.data,
      });

      // Handle response
      const messageHandler = (message: any) => {
        if (message.id === task.id) {
          clearTimeout(timeoutId);
          workerInfo.worker.off('message', messageHandler);

          if (message.error) {
            task.reject(new Error(message.error));
          } else {
            task.resolve(message.result);
          }

          workerInfo.busy = false;
          this.processNextTask();
        }
      };

      workerInfo.worker.on('message', messageHandler);
    } catch (error) {
      clearTimeout(timeoutId);
      task.reject(error as Error);
      workerInfo.busy = false;
      this.processNextTask();
    }
  }

  /**
   * Submit a task to the pool
   */
  async submit<T = any, R = any>(
    type: string,
    data: T,
    timeout?: number
  ): Promise<R> {
    return new Promise((resolve, reject) => {
      const task: WorkerTask<T, R> = {
        id: `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
        type,
        data,
        resolve,
        reject,
        timeout,
      };

      // Try to execute immediately
      const workerId = this.getAvailableWorker();
      if (workerId !== null) {
        this.executeTask(workerId, task);
      } else {
        // Queue the task
        this.taskQueue.push(task);
        logger.info('Task queued', { taskId: task.id, queueLength: this.taskQueue.length });
      }
    });
  }

  /**
   * Get pool statistics
   */
  getStatistics() {
    const workers = Array.from(this.workers.values());

    return {
      totalWorkers: this.workers.size,
      busyWorkers: workers.filter(w => w.busy).length,
      idleWorkers: workers.filter(w => !w.busy).length,
      queuedTasks: this.taskQueue.length,
      totalTasksProcessed: workers.reduce((sum, w) => sum + w.taskCount, 0),
    };
  }

  /**
   * Shutdown the pool
   */
  async shutdown(): Promise<void> {
    logger.info('Shutting down worker pool');

    // Terminate all workers
    for (const [workerId, workerInfo] of this.workers.entries()) {
      try {
        await workerInfo.worker.terminate();
      } catch (error) {
        logger.error('Failed to terminate worker', { workerId, error });
      }
    }

    this.workers.clear();
    this.taskQueue = [];

    logger.info('Worker pool shut down');
  }
}

